﻿using System.Windows.Controls;
using System.Linq;
using System.Collections.Generic;
using System.Windows;
using System.Text;
using System.Windows.Markup;
using System;
using System.Windows.Media;
using System.Windows.Input;

namespace Fayshine.VA.Control
{
    public class FayshineDataGrid : DataGrid
    {

        public event RowClickedHandler RowClicked;
        public event RowDoubleClickedHandler RowDoubleClicked;

        public delegate void RowClickedHandler(object sender, DataGridRowClickedArgs e);
        public delegate void RowDoubleClickedHandler
            (object sender, DataGridRowClickedArgs e);

        private DataGridRow _LastDataGridRow = null;
        private DataGridColumn _LastDataGridColumn = null;
        private DataGridCell _LastDataGridCell = null;
        private object _LastObject = null;
        private DateTime _LastClick = DateTime.MinValue;

        private const string HeaderCheckBoxName = "select_column_checkbox";
        private Dictionary<object, MarkObject> _markObjects;
        private DataGridTemplateColumn _selectColumn;
        private CheckBox _selectCheckBox;

        private double _DoubleClickTime = 1000;
        public double DoubleClickTime
        {
            get
            {
                return _DoubleClickTime;
            }
            set
            {
                _DoubleClickTime = value;
            }
        }

        /// <summary>
        /// DisplayMemberPath is a simple way to define a default template
        /// that describes how to convert Items into UI elements by using
        /// the specified path.
        /// </summary>
        public bool ShowCheckColumn
        {
            get { return (bool)GetValue(ShowCheckColumnProperty); }
            set { SetValue(ShowCheckColumnProperty, value); }
        }

        /// <summary>
        ///     The DependencyProperty for the DisplayMemberPath property.
        /// </summary>
        public static readonly DependencyProperty ShowCheckColumnProperty =
               DependencyProperty.Register("ShowCheckColumn", typeof(bool), typeof(FayshineDataGrid), new PropertyMetadata(true,new PropertyChangedCallback(ShowCheckColumnPropertyChangedCallBack)));


        public static void ShowCheckColumnPropertyChangedCallBack(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            FayshineDataGrid a = sender as FayshineDataGrid;
            if (a != null)
            {
                a.ResetCheckColumn();
            }
        }

        public FayshineDataGrid()
        {
            _markObjects = new Dictionary<object, MarkObject>();

            _selectColumn = new DataGridTemplateColumn();
            _selectColumn.HeaderStyle = GetHeaderStyle();
            _selectColumn.CellTemplate = GetCellTemplate();
            if (ShowCheckColumn)
            {
                this.Columns.Insert(0, _selectColumn);
            }
            this.SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
            this.MouseLeftButtonUp += new MouseButtonEventHandler(DoubleClickDataGridClick);
            
        }

        private void ResetCheckColumn()
        {
            if (!this.ShowCheckColumn)
            {
                DataGridTemplateColumn firstColumn = this.Columns[0] as DataGridTemplateColumn;

                if (firstColumn != null && firstColumn == _selectColumn)
                {
                    this.Columns.RemoveAt(0);
                }
            }
        }

        public void SelectAll()
        {
            if (_selectCheckBox != null)
                _selectCheckBox.IsChecked = true;
            SetAllSelectedStates(true);
        }

        public void UnselectAll()
        {
            if (_selectCheckBox != null)
                _selectCheckBox.IsChecked = false;
            SetAllSelectedStates(false);
        }

        public List<T> GetSelectedItems<T>()
        {
            List<T> result = new List<T>();
            if (ItemsSource != null)
            {
                var enu = ItemsSource.GetEnumerator();
                while (enu.MoveNext())
                {
                    if (GetMarkObject(enu.Current).Selected)
                        result.Add((T)enu.Current);
                }
            }
            return result;
        }

        protected override void OnLoadingRow(DataGridRowEventArgs e)
        {
            base.OnLoadingRow(e);

            if (ShowCheckColumn && _selectColumn != null)
            {
                object dataContext = e.Row.DataContext;
                FrameworkElement element = _selectColumn.GetCellContent(e.Row);
                element.DataContext = GetMarkObject(dataContext);
            }
        }

        private Style GetHeaderStyle()
        {
            Style style = new System.Windows.Style();
            style.TargetType = typeof(ContentControl);

            StringBuilder tmp = new StringBuilder();
            tmp.Append("<DataTemplate ");
            tmp.Append("xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
            tmp.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' >");
            tmp.Append(string.Format("<CheckBox x:Name='{0}' VerticalAlignment='Center' HorizontalAlignment='Center' />", HeaderCheckBoxName));
            tmp.Append("</DataTemplate>");
            DataTemplate contentTemplate = XamlReader.Load(tmp.ToString()) as DataTemplate;

            style.Setters.Add(new Setter(ContentControl.ContentTemplateProperty, contentTemplate));
            return style;
        }

        private DataTemplate GetCellTemplate()
        {
            StringBuilder tmp = new StringBuilder();
            tmp.Append("<DataTemplate ");
            tmp.Append("xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
            tmp.Append("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' >");
            tmp.Append("<CheckBox IsChecked='{Binding Selected,Mode=TwoWay}' VerticalAlignment='Center' HorizontalAlignment='Center' />");
            tmp.Append("</DataTemplate>");
            return XamlReader.Load(tmp.ToString()) as DataTemplate;
        }

        private MarkObject GetMarkObject(Object obj)
        {
            if (_markObjects.ContainsKey(obj) == false)
            {
                MarkObject markObject;
                markObject = new MarkObject();
                _markObjects.Add(obj, markObject);
            }

            return _markObjects[obj];
        }

        private void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            _selectCheckBox = this.GetChild<CheckBox>(HeaderCheckBoxName);
            if (_selectCheckBox == null)
                return;

            _selectCheckBox.Checked += (sender2, e2) => SetAllSelectedStates(true);
            _selectCheckBox.Unchecked += (sender2, e2) => SetAllSelectedStates(false);
        }

        private void SetAllSelectedStates(bool value)
        {
            if (ItemsSource == null)
                return;

            var enu = ItemsSource.GetEnumerator();
            while (enu.MoveNext())
            {
                GetMarkObject(enu.Current).Selected = value;
            }
        }

        #region Double Click

        protected void OnRowClicked()
        {
            if (RowClicked != null)
            {
                RowClicked(this, new DataGridRowClickedArgs(_LastDataGridRow, _LastDataGridColumn, _LastDataGridCell, _LastObject));
            }
        }

        protected void OnRowDoubleClicked()
        {
            if (RowDoubleClicked != null)
            {
                RowDoubleClicked(this, new DataGridRowClickedArgs(_LastDataGridRow, _LastDataGridColumn, _LastDataGridCell, _LastObject));
            }
        }

        private void DoubleClickDataGridClick(object sender, MouseButtonEventArgs e)
        {
            DateTime clickTime = DateTime.Now;
            DataGridRow currentRowClicked;
            DataGridColumn currentColumnClicked;
            DataGridCell currentCellClicked;
            object currentObject;

            //If we've found at least the row,
            if (GetDataGridCellByPosition(e.GetPosition(null), out currentRowClicked,
                  out currentColumnClicked, out currentCellClicked, out currentObject))
            {
                //And the current row is the same as the last row, and is within 
                //the timespan, consider it a double-click
                bool isDoubleClick = (currentRowClicked == _LastDataGridRow &&
                        clickTime.Subtract(_LastClick) <= TimeSpan.FromMilliseconds(_DoubleClickTime));

                _LastDataGridRow = currentRowClicked;
                _LastDataGridColumn = currentColumnClicked;
                _LastDataGridCell = currentCellClicked;
                _LastObject = currentObject;

                if (isDoubleClick)
                {
                    OnRowDoubleClicked();
                }
                else
                {
                    OnRowClicked();
                }
            }
            else
            {
                _LastDataGridRow = null;
                _LastDataGridCell = null;
                _LastDataGridColumn = null;
                _LastObject = null;
            }

            _LastClick = clickTime;
        }

        private bool GetDataGridCellByPosition(Point pt, out DataGridRow dataGridRow,
                     out DataGridColumn dataGridColumn, out DataGridCell dataGridCell,out object dataGridObject)
        {
            var elements = VisualTreeHelper.FindElementsInHostCoordinates(pt, this);
            dataGridRow = null;
            dataGridCell = null;
            dataGridColumn = null;
            dataGridObject = null;

            if (null == elements || elements.Count() == 0)
            {
                return false;
            }

            var rowQuery = from gridRow in elements
                           where gridRow
                               is DataGridRow
                           select gridRow as DataGridRow;
            dataGridRow = rowQuery.FirstOrDefault();
            if (dataGridRow == null)
            {
                return false;
            }

            dataGridObject = dataGridRow.DataContext;

            var cellQuery = from gridCell in elements
                            where gridCell is DataGridCell
                            select gridCell as DataGridCell;
            dataGridCell = cellQuery.FirstOrDefault();

            if (dataGridCell != null)
            {
                dataGridColumn = DataGridColumn.GetColumnContainingElement(dataGridCell);
            }

            //If we've got the row, return true - 
            //sometimes the Column, DataContext could be null
            return dataGridRow != null;
        }

        #endregion

    }

    public class DataGridRowClickedArgs
    {
        public DataGridRow DataGridRow { get; set; }
        public DataGridColumn DataGridColumn { get; set; }
        public DataGridCell DataGridCell { get; set; }
        public object DataGridRowItem { get; set; }

        public DataGridRowClickedArgs(DataGridRow dataGridRow,
        DataGridColumn dataGridColumn, DataGridCell dataGridCell, object dataGridRowItem)
        {
            DataGridRow = dataGridRow;
            DataGridColumn = dataGridColumn;
            DataGridCell = dataGridCell;
            DataGridRowItem = dataGridRowItem;
        }
    }

    public static class FayshineDataGridExtension
    {
        public static T GetChild<T>(this DependencyObject obj, string name = null) where T : FrameworkElement
        {
            DependencyObject child = null;
            T grandChild = null;
            for (int i = 0; i <= VisualTreeHelper.GetChildrenCount(obj) - 1; i++)
            {
                child = VisualTreeHelper.GetChild(obj, i);
                if (child is T && (((T)child).Name == name | string.IsNullOrEmpty(name)))
                {
                    return (T)child;
                }
                else
                {
                    grandChild = GetChild<T>(child, name);
                    if (grandChild != null)
                        return grandChild;
                }
            }
            return null;
        }
    }
}
